package utils

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"encoding/asn1"
	"errors"
	"fmt"
	"math/big"
)

/**
ECDSA签名 会返回一堆R,S 的big.Int的数据
*/
type ECDSASignature struct {
	R, S *big.Int
}

// 曲线簇 这个map必须保证签名中的S值必须小于等于N的一半,只能接受更低的签名
// 它们是为了提高效率而预先计算的.
var curveHalfOrders = map[elliptic.Curve]*big.Int{
	elliptic.P224(): new(big.Int).Rsh(elliptic.P224().Params().N, 1),
	elliptic.P256(): new(big.Int).Rsh(elliptic.P256().Params().N, 1),
	elliptic.P384(): new(big.Int).Rsh(elliptic.P384().Params().N, 1),
	elliptic.P521(): new(big.Int).Rsh(elliptic.P521().Params().N, 1),
}

// 得到曲线簇的N的一般
func GetCurveHalfOrdersAt(c elliptic.Curve) *big.Int {
	return big.NewInt(0).Set(curveHalfOrders[c])
}

// 返回ECDSASignature(r,s)的asn1编码形式的值
func MarshalECDSASignature(r, s *big.Int) ([]byte, error) {
	return asn1.Marshal(ECDSASignature{r, s})
}

// Unmarshal 签名 asn1编码形式
func UnmarshalECDSASignature(raw []byte) (*big.Int, *big.Int, error) {
	// 将ECDSASignature
	sig := new(ECDSASignature)
	// 根据asn1 unmarshal
	_, err := asn1.Unmarshal(raw, sig)
	if err != nil {
		return nil, nil, fmt.Errorf("failed unmashalling signature [%s]", err)
	}

	//校验R、S是否合法
	if sig.R == nil {
		return nil, nil, errors.New("invalid signature, R must be different from nil")
	}
	if sig.S == nil {
		return nil, nil, errors.New("invalid signature, S must be different from nil")
	}

	//R、S必须大于0
	if sig.R.Sign() != 1 {
		return nil, nil, errors.New("invalid signature, R must be larger than zero")
	}
	if sig.S.Sign() != 1 {
		return nil, nil, errors.New("invalid signature, S must be larger than zero")
	}

	return sig.R, sig.S, nil
}

// 将签名中的s变换为 N/2以下
func SignatureToLowS(k *ecdsa.PublicKey, signature []byte) ([]byte, error) {
	r, s, err := UnmarshalECDSASignature(signature)
	if err != nil {
		return nil, err
	}
	//将s
	s, err = ToLowS(k, s)
	if err != nil {
		return nil, err
	}

	return MarshalECDSASignature(r, s)
}

// 判断s 是否是对应曲线N/s
func IsLowS(k *ecdsa.PublicKey, s *big.Int) (bool, error) {
	halfOrder, ok := curveHalfOrders[k.Curve]
	if !ok {
		return false, fmt.Errorf("curve not recognized [%s]", k.Curve)
	}

	return s.Cmp(halfOrder) != 1, nil
}

// 将s变换为N/2以下
func ToLowS(k *ecdsa.PublicKey, s *big.Int) (*big.Int, error) {
	lowS, err := IsLowS(k, s)
	if err != nil {
		return nil, err
	}

	if !lowS {
		// 如果没有小于,将s设置为 N-s
		s.Sub(k.Params().N, s)

		return s, nil
	}

	return s, nil
}
